#include "lua_function.h"

#include <stdlib.h>
#include <string.h>

#include <event.h>

#include <assert.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>

#include <tchdb.h>

#include "log.h"

#include "limit_resources_manager.h"

#define UNUSED(x) ((void*)(x))

#define LUA_CALL(L, IN, OUT) lua_call(L, IN, OUT)

static struct event_base * main_base = 0;

static int bin_to_int(lua_State * L)
{
	if(!lua_isstring(L, -1) ) {
		return 0;
	}

	size_t len = 0;
	const char * ptr = lua_tolstring(L, -1, &len);
	if (len < sizeof(int32_t)) {
		return 0;
	}
	int32_t val = 0;
	memcpy(&val, ptr, sizeof(val));
	lua_pushinteger(L, val);
	return 1;
}

static int bin_from_int(lua_State * L)
{
	if(!lua_isnumber(L, -1) ) {
		return 0;
	}

	int32_t val = lua_tointeger(L, -1);
	lua_pushlstring(L, (const char*)&val, sizeof(val));
	return 1;
}

static size_t bin_encode_type_and_len(int type, size_t val_len,
		char * buff, size_t len)
{
	if (len < sizeof(uint32_t)) { return 0; }
	if (type > 255 && type < 0) { return 0; }
	if (val_len > 0xffffff) { return 0; }
	
	buff[0] = type;
	buff[1] = (val_len & 0xff0000) >> 16;
	buff[2] = (val_len & 0xff00) >> 8;
	buff[3] = val_len & 0xff;
	return sizeof(uint32_t);
}

static size_t bin_encode_nil(lua_State * L, int index,
		char * buff, size_t len)
{
	assert(lua_isnil(L, index));

	if (len < 1) { return 0; }

	buff[0] = LUA_TNIL;
	return 1;
}

static size_t bin_encode_boolean(lua_State * L, int index,
		char * buff, size_t len)
{
	assert(lua_isboolean(L, index));

	if (len < 2) { return 0; }

	buff[0] = LUA_TBOOLEAN;
	buff[1] = lua_toboolean(L, index) ? 1 : 0;
	return 2;
}

static size_t bin_encode_int(lua_State * L, int index,
		char * buff, size_t len)
{
	assert(lua_isnumber(L, index));

	LUA_INTEGER val = lua_tointeger(L, index);

	if (len < (sizeof(uint32_t) + 4)) { return 0; }

	bin_encode_type_and_len(LUA_TNUMBER, 4, buff, len);
	char * ptr = buff + sizeof(uint32_t);
	ptr[0] = (val & 0xff000000) >> 24;
	ptr[1] = (val & 0xff0000) >> 16;
	ptr[2] = (val & 0xff00) >> 8;
	ptr[3] = val & 0xff;
	return sizeof(uint32_t) + 4;
}

static size_t bin_encode_string(lua_State * L, int index,
		char * buff, size_t len)
{
	size_t str_len = 0;
	const char * ptr = lua_tolstring(L, index, &str_len);
	if (ptr == 0 || len < sizeof(uint32_t) + str_len) {
		return 0;
	}

	if (bin_encode_type_and_len(LUA_TSTRING, str_len, buff, len) == 0) {
		return 0;
	}

	memcpy(buff + sizeof(uint32_t), ptr, len);
	return sizeof(uint32_t) + str_len;
}

static size_t bin_encode_value(lua_State * L, int index,
		char * buff, size_t len);

static size_t bin_encode_table(lua_State * L, int index,
		char * buff, size_t len)
{
	if (len < sizeof(uint32_t)) {
		return 0;
	}

	char * content = buff + sizeof(uint32_t);;
	len -= sizeof(uint32_t);
	size_t table_len = 0;

	lua_pushnil(L);
	while(lua_next (L, index-1)) {
		size_t val_len = bin_encode_value(L, -2, content, len);
		if (val_len == 0) {
			return 0;
		}
		content += val_len;
		len -= val_len;
		table_len += val_len;

		val_len = bin_encode_value(L, -1, content, len);
		if (val_len == 0) {
			return 0;
		}
		content += val_len;
		len -= val_len;
		table_len += val_len;

		lua_pop(L, 1);
	}

	bin_encode_type_and_len(LUA_TTABLE, table_len, buff, sizeof(uint32_t));
	return table_len + sizeof(uint32_t);
}

static size_t bin_encode_value(lua_State * L, int index,
		char * buff, size_t len)
{
	size_t val_len = 0;
	int type = lua_type(L, index);
	switch(type) {
		case LUA_TNIL:
			val_len = bin_encode_nil(L, index, buff, len);
			break;
		case LUA_TBOOLEAN:
			val_len = bin_encode_boolean(L, index, buff, len);
			break;
		case LUA_TNUMBER:
			val_len = bin_encode_int(L, index, buff, len);
			break;
		case LUA_TSTRING:
			val_len = bin_encode_string(L, index, buff, len);
			break;
		case LUA_TTABLE:
			val_len = bin_encode_table(L, index, buff, len);
			break;
		case LUA_TFUNCTION:
		case LUA_TUSERDATA:
		case LUA_TTHREAD:
		case LUA_TLIGHTUSERDATA:
		default:
			break;
	}
	return val_len;
}


static int bin_encode(lua_State * L)
{
	char buff[4096] = {0};
	size_t len = 0;

	len = bin_encode_value(L, -1, buff, 4096);
	if (len == 0) {
		return 0;
	}
	lua_pushlstring(L, buff, len);
	return 1;
}

static uint32_t read_len(const char * buff, size_t len)
{
	if (len < sizeof(uint32_t)) { return 0; }

	uint32_t l = 0;
	int i = 0;
	for(i = 1; i < 4; i++) {
		l = l << 8 | buff[i];
	}
	return l;
}

static size_t bin_decode_nil(lua_State * L, const char * buff, size_t len)
{
	if (len == 0) return 0;
	assert(buff[0] == LUA_TNIL);
	lua_pushnil(L);
	return 1;
}

static size_t bin_decode_boolean(lua_State* L, const char * buff, size_t len)
{
	if (len < 2) return 0;
	assert(buff[0] == LUA_TBOOLEAN);
	int b = buff[1];
	lua_pushboolean(L, b);
	return 2;
}

static size_t bin_decode_int(lua_State * L, const char * buff, size_t len)
{
	if (len < sizeof(uint32_t)) return 0;
	assert(buff[0] == LUA_TNUMBER);
	size_t val_len = read_len(buff, len);
	assert(val_len == sizeof(uint32_t));

	if (sizeof(uint32_t) + val_len > len) return 0;

	LUA_INTEGER val = 0;

	size_t i = 0;
	for(i = 0; i < val_len; i++) {
		val = (val << 8) | buff[sizeof(uint32_t)+i];
	}
	lua_pushinteger(L, val);
	return sizeof(uint32_t) + val_len;
}

static size_t bin_decode_string(lua_State * L, const char * buff, size_t len)
{
	if (len < sizeof(uint32_t)) return 0;

	assert(buff[0] == LUA_TSTRING);
	size_t val_len = read_len(buff, len);

	if (sizeof(uint32_t) + val_len > len) return 0;

	lua_pushlstring(L, buff + sizeof(uint32_t), val_len);
	return sizeof(uint32_t) + val_len;
}

static size_t bin_decode_value(lua_State * L, const char * buff, size_t len);
static size_t bin_decode_table(lua_State * L, const char * buff, size_t len)
{
	assert(buff[0] == LUA_TTABLE);
	uint32_t table_len = read_len(buff, len);
	if (table_len == 0 || len < table_len + sizeof(uint32_t)) {
		return 0;
	}

	lua_newtable(L);
	const char * ptr = buff + sizeof(uint32_t);
	size_t left_len = len - sizeof(uint32_t);
	while(left_len > 0) {
		//read key
		size_t read_len = bin_decode_value(L, ptr, left_len);
		if (read_len == 0) {
			//pop table
			lua_pop(L, 1);
			return 0;
		}

		ptr += read_len;
		left_len -= read_len;

		//read val
		read_len = bin_decode_value(L, ptr, left_len);
		if (read_len == 0) {
			//pop key and table
			lua_pop(L, 2);
			return 0;
		}
		ptr += read_len;
		left_len -= read_len;

		lua_settable(L, -3);
	}
	return sizeof(uint32_t) + table_len;
}

static size_t bin_decode_value(lua_State * L, const char * buff, size_t len)
{
	if (len == 0) { return 0; }
	int type = buff[0];
	switch(type) {
		case LUA_TNIL:
			return bin_decode_nil(L, buff, len);
		case LUA_TBOOLEAN:
			return bin_decode_boolean(L, buff, len);
		case LUA_TNUMBER:
			return bin_decode_int(L, buff, len);
		case LUA_TSTRING:
			return bin_decode_string(L, buff, len);
			break;
		case LUA_TTABLE:
			return bin_decode_table(L, buff, len);
		case LUA_TFUNCTION:
		case LUA_TUSERDATA:
		case LUA_TTHREAD:
		case LUA_TLIGHTUSERDATA:
		default:
			return 0;
	}
}

static int bin_decode(lua_State * L)
{
	if (!lua_isstring(L, -1)) {
		return 0;
	}

	size_t len = 0;
	const char * str = lua_tolstring(L, -1, &len);

	int val_count = 0;
	while(len > 0) {
		size_t read_len = bin_decode_value(L, str, len);
		if (read_len == 0) {
			if (val_count > 0) {
				lua_pop(L, val_count);
				return 0;
			}
		}
		str += read_len;
		len -= read_len;
		val_count++;
	}
	return val_count;
}

luaL_Reg bin_functions[] = {
	{"to_int", bin_to_int},
	{"from_int", bin_from_int},
	{"encode", bin_encode},
	{"decode", bin_decode},
	{0,0},
};

int script_exit = 0;

static int loop_exit(lua_State * L)
{
	event_base_loopbreak(main_base);
	script_exit = 1;
	return 0;
}

luaL_Reg loop_functions[] = {
	{"exit", loop_exit},
	{0,0},
};

int registe_base_function(lua_State * L, struct event_base * base)
{
	main_base = base;
	luaL_register(L, "bin", bin_functions);
	luaL_register(L, "loop", loop_functions);

	return 0;
}
